/**
 *      Handles all user related transactions which includes registering new users,
 *  fetching all or a specific user, etc.
 *  
 *  *IMPORTANT
 *      When using this script, set first the database where the functions will
 *  execute using the setDatabase function.
 */

var express = require('express')
        , mysql = require('mysql')
        , config = require('.././config')
        , utilities = require('./utilities');

//  classes
var User = require('.././models/User');

var db;

//------------------------------------------------------------------------------
//  Static fields/constants (Application Scoped)

var success = "Success";
exports.SUCCESS = success;

//------------------------------------------------------------------------------
//  Connection Functions

exports.setDatabase = function(database) {
    db = database;
};

//------------------------------------------------------------------------------
//  Utility Functions

/**
 *  Converts a mysql response object to a User object
 * @param {type} result the response object from MySQL
 * @returns {User} the user object converted
 */
function resultToUser(result) {
    var user = new User();
    user.id = result.id;
    user.firstName = result.first_name;
    user.lastName = result.last_name;
    user.email = result.email;
    user.role = result.role;
    user.availability = result.availability;

    return user;
}

//------------------------------------------------------------------------------
//  User Authentication Functions (Core Functionalities)

/**
 *  Checks if the passed credentials (email and password) are valid, results are
 * passed to the parameter of the callback function. The result passed is an object
 * containing both the id of the user authenticated and his role.
 * @param {String} email 
 * @param {String} password
 * @param {function} callback
 * @returns {undefined}
 */
exports.authenticate = function(email, password, callback) {
    //  validation
    if (typeof callback !== 'function') {
        //  since the error cannot be handled because of the absence of a callback
        //  function, just throw the error.
        throw new Error("Invalid callback passed to getUserById - " + utilities.getDateTime());
    }

    if (!email) {
        callback(new Error('Unspecified email when logging in.'));
    }

    if (!password) {
        callback(new Error('Unspecified password when logging in.'));
    }

    //  end of validation

    //  query returns the id of the user if the credentials are valid, otherwise,
    //  the string 'Incorrect' is returned by the query.
    //  in case the email is not found, there will be no result set returned.
    var query = "SELECT (IF (password = '" + utilities.getMD5(password) + "', id, 'Incorrect')) data, role \n\
                    FROM user WHERE email = '" + email + "';";

    db.query(query, function(err, result) {
        if (err) {
            callback(err);
        } else {
            if (result) {
                if (result.length >= 1) {
                    if (result[0].data === 'Incorrect') {
                        callback(new Error('Incorrect password'));
                    } else {
                        callback({id: result[0].data, role: result[0].role});
                    }
                } else {
                    callback(new Error('E-Mail not found'));
                }
            } else {
                callback(new Error('MySQL did not respond'));
            }
        }
    });
};

//------------------------------------------------------------------------------
//  User Data Creators

/**
 * 
 *  Inserts a new user to the mysql database. The result of the insert will be
 * passed to the parameter of the callback function which can either be an error
 * object containing the error message or a success message.
 * 
 * @param {User} user 
 *      the user object to register/insert to the database
 * @param {function} callback 
 *      the callback method that will handle errors passed by 
 *      this method, it should accept one parameter which contains
 *      the error message (Ex.) userManager.register(userObject, function (err) {...}).
 *      This will also contain the success message if the registration is successful.
 * @returns {undefined}
 */
exports.register = function(user, callback) {
    var unsuppliedFields = "";
    var queryValues = "";
    var query = 'INSERT INTO user(first_name, last_name, email, password, role, availability, Company_id) VALUES ';

    //---------------------
    //  error checking and validation

    //  validation
    if (typeof callback !== 'function') {
        //  since the error cannot be handled because of the absence of a callback
        //  function, just throw the error.
        throw new Error("Invalid callback passed to getUserById - " + utilities.getDateTime());
    }

    //  check if the the object passed is an instance of User    
    if (!(user instanceof User)) {
        callback(new Error("Unable to register user, the specified object is not an intance of the User object"));
    }

    //  check for the required fields
    if (!user.firstName) {
        unsuppliedFields += "first name, ";
    }
    if (!user.lastName) {
        unsuppliedFields += "last name, ";
    }
    if (!user.email) {
        unsuppliedFields += "email, ";
    }
    if (!user.password) {
        unsuppliedFields += "password, ";
    }

    if (unsuppliedFields !== "") {
        unsuppliedFields = unsuppliedFields.substring(0, unsuppliedFields.length - 2);

        callback(new Error("Unable to register user, some fields are missing (" + unsuppliedFields + ")"));
    }

    if (!db) {
        callback(new Error("Disconnected from MySQL server - " + utilities.getDateTime()));
    }

    //---------------------
    //  table insertion

    queryValues = "'" + user.firstName + "', "
            + "'" + user.lastName + "', "
            + "'" + user.email + "', "
            + "'" + utilities.getMD5(user.password) + "', "
            + "'" + user.role + "', "
            + "'" + user.availability + "', "
            + "'" + user.companyId + "'";

    query += "(" + queryValues + ")";

    db.query(query, function(err) {
        if (err) {
            //  check for any errors in the MySQL.
            //  if the error is caused by duplicate email, specify it, otherwise
            //  just return the error object
            if (err.message.indexOf("ER_DUP_ENTRY") === 0) {
                callback(new Error('The email specified is already in use of another user.'));
            } else {
                callback(err);
            }
        } else {
            callback(success);
        }
    });
};

//------------------------------------------------------------------------------
//  User Data Fetchers

/**
 * 
 *  Gets a user object from the database using the specified id of the user, the
 * user object may be fetched using the parameter passed to the callback function
 * passed to this method. If there are any errors in fetching the user, an error
 * object is passed to the parameter instead of a User object.
 * 
 * @param {integer} id          the id of the user to fetch
 * @param {function} callback   the callback function to be called after execution of this method
 * @returns {undefined}
 */
exports.getUserById = function(id, callback) {
    var query = "SELECT * FROM user WHERE id=" + id;

    //  validation
    if (typeof callback !== 'function') {
        //  since the error cannot be handled because of the absence of a callback
        //  function, just throw the error.
        throw new Error("Invalid callback passed to getUserById - " + utilities.getDateTime());
    }

    if (!db) {
        callback(new Error("Disconnected from MySQL server - " + utilities.getDateTime()));
    }

    db.query(query, function(err, result) {
        if (err) {
            callback(err);
        }

        if (result.length >= 1) {
            //  user found
            callback(resultToUser(result[0]));
        } else {
            callback(new Error('Empty response'));
        }
    });
};

/**
 * 
 *  Gets an array of User objects from all the users in the database, the array
 * may be fetched using the parameter passed to the callback function passed to
 * this method. If there are any errors in fetching the users, an error object 
 * is passed to the parameter instead of an array of user objects.
 * 
 * @param {function} callback 
 *      the callback function to be called after execution of this method
 * @param {Array(Int)} specificUserIdList 
 *      optional parameter that contains an array of integers, specifying the id 
 *      of the users
 *                          
 * @returns {undefined}
 */
exports.getUsers = function(callback, specificUserIdList) {
    var query = "SELECT id, first_name, last_name, email, role FROM user";
    var users = [];

    //  validation
    if (typeof callback !== 'function') {
        //  since the error cannot be handled because of the absence of a callback
        //  function, just throw the error.
        throw new Error("Invalid callback passed to getUsers - " + utilities.getDateTime());
    }

    if (!db) {
        callback(new Error("Disconnected from MySQL server - " + utilities.getDateTime()));
    }

    //  if there are specified users
    if (specificUserIdList) {
        if (Object.prototype.toString.call(specificUserIdList) === '[object Array]') {
            query += " WHERE id IN (";

            for (var i in specificUserIdList) {
                query += "'" + specificUserIdList[i] + "',";
            }

            query = query.substring(0, query.length - 1) + ")";
        }
    }

    db.query(query, function(err, result) {
        if (err) {
            callback(err);
        }

        if (result.length >= 1) {
            for (var i in result) {
                users.push(resultToUser(result[i]));
            }

            callback(users);
        } else {
            callback(emptyResponse);
        }
    });
};

/**
 * 
 *  Gets all the users of a specified company. If there are any errors in fetching 
 * the users, an error object is passed to the parameter instead of an array of 
 * user objects.
 * 
 * @param {String} company 
 *      the name of the company to search
 * @param {function} callback 
 *      the callback function to be called after execution of this method 
 * @param {Array(Int)} specificUserIdList 
 *      optional parameter that contains an array of integers, specifying the id 
 *      of the users
 * @returns {undefined}
 */
exports.getUsersByCompany = function(company, callback, specificUserIdList) {
    //  validation
    if (typeof callback !== 'function') {
        //  since the error cannot be handled because of the absence of a callback
        //  function, just throw the error.
        throw new Error("Invalid callback passed to getCompanyUsers - " + utilities.getDateTime());
    }

    if (!db) {
        callback(new Error("Disconnected from MySQL server - " + utilities.getDateTime()));
    }

    if (!company) {
        callback(new Error("No company name specified"));
    }

    var query = "SELECT u.id, u.first_name, u.last_name, u.email, u.role, u.availability FROM user u " +
            "LEFT JOIN company c ON u.Company_id=c.id " +
            "WHERE c.name='" + company + "'";

    var users = [];

    //  if there are specified users
    if (specificUserIdList) {
        if (Object.prototype.toString.call(specificUserIdList) === '[object Array]') {
            query += " WHERE u.id IN (";

            for (var i in specificUserIdList) {
                query += "'" + specificUserIdList[i] + "',";
            }

            query = query.substring(0, query.length - 1) + ")";
        }
    }

    db.query(query, function(err, result) {
        if (err) {
            callback(err);
        }

        if (result || result.length >= 1) {
            for (var i in result) {
                users.push(resultToUser(result[i]));
            }

            callback(users);
        } else {
            callback(new Error('Empty response'));
        }
    });
};

/**
 *  Gets the users available for chat (or online) of a specified company. If 
 * there are any errors in fetching the users, an error object is passed to the 
 * parameter instead of an array of user objects.
 * 
 * @param {String} company 
 *      the name of the company to search
 * @param {function} callback 
 *      the callback function to be called after execution of this method 
 * @param {Array(Int)} specificUserIdList 
 *      optional parameter that contains an array of integers, specifying the id 
 *      of the users
 * @returns {undefined}
 */
exports.getAvailableUsersByCompany = function(company, callback, specificUserIdList) {
    //  validation
    if (typeof callback !== 'function') {
        //  since the error cannot be handled because of the absence of a callback
        //  function, just throw the error.
        throw new Error("Invalid callback passed to getCompanyAvailableUsers - " + utilities.getDateTime());
    }

    if (!db) {
        callback(new Error("Disconnected from MySQL server - " + utilities.getDateTime()));
    }

    if (!company) {
        callback(new Error("No company name specified"));
    }

    var query = "SELECT u.id, u.first_name, u.last_name, u.email, u.role, u.availability FROM user u " +
            "LEFT JOIN company c ON u.Company_id=c.id " +
            "WHERE c.name='" + company + "' AND u.availability ='online'"; // IMPORTANT online is hard coded, replace this later

    var users = [];

    //  if there are specified users
    if (specificUserIdList) {
        if (Object.prototype.toString.call(specificUserIdList) === '[object Array]') {
            query += " WHERE u.id IN (";

            for (var i in specificUserIdList) {
                query += "'" + specificUserIdList[i] + "',";
            }

            query = query.substring(0, query.length - 1) + ")";
        }
    }

    db.query(query, function(err, result) {
        if (err) {
            callback(err);
        }

        if (result.length >= 1) {
            for (var i in result) {
                users.push(resultToUser(result[i]));
            }

            callback(users);
        } else {
            callback(new Error('Empty response'));
        }
    });
};

//------------------------------------------------------------------------------
//  User Data Modifiers

/**
 *  Modifies a single value of a specified user in the database. Any error
 * is specified in the parameter passed to the callback function.
 * 
 * @param {type} id the id of the user to modify
 * @param {type} fieldName  the name of the field in MySQL
 * @param {type} newValue the new value of the field to set
 * @param {type} callback the callback function to be called after execution of this method 
 * @returns {undefined}
 */
function modifyUser(id, fieldName, newValue, callback) {
    //  validation
    if (typeof callback !== 'function') {
        //  since the error cannot be handled because of the absence of a callback
        //  function, just throw the error.
        throw new Error("Invalid callback passed to getCompanyAvailableUsers - " + utilities.getDateTime());
    }

    if (!db) {
        callback(new Error("Disconnected from MySQL server - " + utilities.getDateTime()));
    }

    if (!id) {
        callback(new Error("No user id specified"));
    }
    if (!fieldName) {
        callback(new Error("No field name to edit specified"));
    }
    if (!newValue) {
        callback(new Error("No value for field specified"));
    }

    var query = "UPDATE user SET " + fieldName + "='" + newValue + "' WHERE id='" + id + "';";

    db.query(query, function(err) {
        if (err) {
            callback(err);
        } else {
            callback(success);
        }
    });
}

exports.modify = modifyUser;

/**
 *  Changes the availability of a specified user in the database. Any error
 * is specified in the parameter passed to the callback function.
 *  
 * @param {type} id the id of the user to modify
 * @param {type} availability the new value of the availability field
 * @param {type} callback the callback function to be called after execution of this method 
 * @returns {undefined}
 */
exports.changeAvailability = function(id, availability, callback) {
    modifyUser(id, 'availability', availability, callback);
};